package ext.tianma.loginAuth;  
  
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;

  
public class TMKeycloakOIDCFilter implements Filter {  
    public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";  
    public static final String CONFIG_PATH_PARAM = "keycloak.config.path";  
    protected KeycloakDeployment keycloakDeployment;  
    protected Pattern skipPattern;  
    private String configFilePath = "codebase" + File.separator + "loginfilter" + File.separator + "loginfilter.properties";
    final Base64 base64 = new Base64();
    @Override  
    public void init(final FilterConfig filterConfig) throws ServletException {  
       //读取初始化参数，白名单（无需认证即可访问的资源） 
        String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);  
        System.out.println("KeycloakOIDCFilter.............skipPatternDefinition:" + skipPatternDefinition);
        if (skipPatternDefinition != null) {  
            skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);  
        } 
        System.out.println("KeycloakOIDCFilter.............skipPattern:" + skipPattern);
        //加载client配置信息  
        String path = "/WEB-INF/keycloak.json";  
        String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);  
        System.out.println("KeycloakOIDCFilter.............pathParam:" + pathParam);
        if (pathParam != null){  
             path = pathParam;  
        }  
        InputStream is = filterConfig.getServletContext().getResourceAsStream(path);  
        keycloakDeployment = createKeycloakDeploymentFrom(is);  
        System.out.println("KeycloakOIDCFilter.............keycloakDeployment:" + keycloakDeployment);
    }  
  
    private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {  
        if (is == null) {  
            System.out.println("No adapter configuration. "  + "Keycloak is unconfigured and will deny all requests.");  
            return new KeycloakDeployment();  
        }  
        return KeycloakDeploymentBuilder.build(is);  
    }  
  
  
    @Override  
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  
    	System.out.println("KeycloakOIDCFilter.............doFilter");
        HttpServletRequest request = (HttpServletRequest) req;  
        HttpServletResponse response = (HttpServletResponse) res;  
        HttpSession session = request.getSession();  
        RefreshableKeycloakSecurityContext context =  (RefreshableKeycloakSecurityContext)request.getAttribute("org.keycloak.KeycloakSecurityContext");  
        String loginName = "";
        String tokenCode = "";
       AccessToken tokenContext = null;
        if(context != null){
        	tokenCode = context.getTokenString();
        	tokenContext = context.getToken();
		    loginName = tokenContext.getPreferredUsername();//登录账号  
		    System.out.println("LoginFilterkeycloak...........loginName:" + loginName);  
	       }
        // 获取用户访问的路径
     	String targetUrl = request.getRequestURL().toString();
     	System.out.println("KeycloakOIDCFilter.............targetUrl:" + request.getRemoteAddr() + ":" + targetUrl);
     	PropertiesUtil ut = new PropertiesUtil(configFilePath);
        String whiteListURL = ut.getValueByKey("whiteListURL");
        System.out.println("KeycloakOIDCFilter.............whiteListURL:" + whiteListURL);
        String auth = request.getHeader("Authorization");
		System.out.println("KeycloakOIDCFilter.............auth:" + auth);
      //白名单URL，直接放
//      if (shouldSkip(request)) {  
//          chain.doFilter(req, res);  
//      }else
        if(isWhiteURL(targetUrl, whiteListURL)){//过滤白名单
        	chain.doFilter(request, response);
		}else if(targetUrl.contains("/netmarkets/jsp/login/login.jsp") && null != auth){//管理员直接登录
			System.out.println("管理员直接登录.................");
			 session.setAttribute("Authorization",auth);
			 session.setAttribute("isFilter", "false");
				 RequestWrap newRequest = new RequestWrap(request);
					newRequest.addHeader("Authorization", auth);
					newRequest.setRemoteUser(auth);
					chain.doFilter(newRequest, res);
					return;
		}else{
        	String sessionAuth = (String) session.getAttribute("Authorization");
    		System.out.println("...............sessionAuth:" + sessionAuth);
    		String isFilter = (String) session.getAttribute("isFilter");
    		System.out.println("...............isFilter:" + isFilter);
    		 if(sessionAuth!=null && "false".equals(isFilter)){//login.jsp go to windchill
    			 RequestWrap newRequest = new RequestWrap(request);
    				newRequest.addHeader("Authorization",sessionAuth);
    				String[] userName = converAuthHeader(sessionAuth);
    				newRequest.setRemoteUser(userName[0]);
    				chain.doFilter(newRequest, res);
    				return;
    			}
    		 if("".equals(tokenCode)){//未登录认证平台
    				String authURL = this.getRedirectURL(request);    
    		          System.out.println("转向keycloak认证");     
    		          System.out.println(".............authURL:" + authURL);
    		          response.sendRedirect(authURL);  
    		          return;  
    		        }
        	//已登录，检查token过期时间  
            Long exp = (long) context.getToken().getExpiration();//Long.parseLong(String.valueOf(accessToken.get("exp")));  
            Long now = System.currentTimeMillis()/1000;  
            //已过期或者离过期时间不足5分钟，则刷新token。  
            if(now > exp || (exp - now) < 1*60){  
                System.out.println(".............now="+now+".............exp="+exp);  
                String url = keycloakDeployment.getAuthServerBaseUrl()  
                        + "/realms/"  
                        + keycloakDeployment.getRealm()  
                        + "/protocol/openid-connect/token";  
                 Map<String, String> params = new HashMap<String, String>();  
                 params.put("refresh_token", String.valueOf(session.getAttribute("refresh_token_str")));  
                 params.put("grant_type", "refresh_token");  
                 params.put("client_id", keycloakDeployment.getResourceName());  
                 params.put("client_secret", String.valueOf(keycloakDeployment.getResourceCredentials().get("secret")));  
                 HttpResponse result = HttpClientUtil.httpPostForm(url, params, null, "UTF-8");  
                 if(result.getStatusCode() != 200){  
                     System.out.println("刷新token出错！"+result.getStatusCode()+ result.getReasonPhrase());  
                     String authURL = this.getRedirectURL(request);   
                     System.out.println("---刷新token出错,重新认证----");   
                     System.out.println(authURL);  
                     response.sendRedirect(authURL);  
                     return;  
                 }  
                 chain.doFilter(request, res);  
                 return;  
            }  
        }
		 if("".equals(loginName)){
  	  chain.doFilter(request, res); 
  	  return;
    }
            //token正常，请求正常往下传递。
            RequestWrap newRequest = new RequestWrap(request);
			newRequest.addHeader("Authorization", "Basic "+ BASE64Encoder.encode((loginName).getBytes()) );
			newRequest.setRemoteUser(loginName);
			chain.doFilter(newRequest, res);
            return;  
        } 
    private boolean shouldSkip(HttpServletRequest request) {  
        if (skipPattern == null) {  
            return false;  
        }  
        String requestPath = request.getRequestURI().substring(request.getContextPath().length());  
        System.out.println("KeycloakOIDCFilter.............requestPath:" + requestPath);
        return skipPattern.matcher(requestPath).matches();  
    }  
    private String getRedirectURL(HttpServletRequest request)   
            throws UnsupportedEncodingException{  
        String callbackURL = URLEncoder.encode(getBaseURL(request), "UTF-8"); 
        String authURL = keycloakDeployment.getAuthServerBaseUrl()  
                + "/realms/"  
                + keycloakDeployment.getRealm()  
                + "/protocol/openid-connect/auth?client_id="  
                + keycloakDeployment.getResourceName()  
                + "&state="  
                + UUID.randomUUID().toString()  
                + "&response_type=code"  
                + "&scope=openid"  
                + "&redirect_uri="  
                + callbackURL;  
        return authURL;  
    }  
      
    private static String getBaseURL(HttpServletRequest request) {  
        String url = request.getScheme()   
                + "://"   
                + request.getServerName()  
                + ":"   
                + request.getServerPort()   
                + request.getContextPath();
        return url;  
    }  
  
    private boolean isWhiteURL(String currentURL,String whiteURLs) { 
		if(StringUtils.isBlank(whiteURLs)){
			return false;
		}
		String[] whiteListURLs = whiteURLs.split(";;;qqq");
        for (String whiteURL : whiteListURLs) {  
            if (currentURL.matches(whiteURL)) {  
                return true;  
            }  
        }  
        return false;  
    }  
    
    private String[] converAuthHeader(String authHeader){
		String[] auths = authHeader.split(" ");
		try {
			String auth = new String(BASE64Encoder.decode(auths[1]));
			return auth.split(":");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}

    @Override  
    public void destroy() {  
    }  
}  